/**
*
* Konrad Beckmann 2011-01-15
* konrad.beckmann@gmail.com
*
*/

#include <iostream>
#include <cstdlib>
#ifdef __APPLE__
#include <GLUT/glut.h> //CHANGE TO GL/ for Linux/Windows
//g++ glut.c -framework OpenGL -framework GLUT <--- build for mac
#else
include <GL/glut.h>
#endif
#include <OpenGl/Gl.h>
using namespace std;

#include <png.h>

#include "texture_png.h"

#define PNG_HEADER_SIZE 8



/**
* Loads a PNG image and returns width, height and raw data in a struct
*
* @param name The path to the file that is to be loaded
* @param keep_alpha Specifies if alpha-channel will be included in output
* @param pixel_size Specifies size of pixels in bytes (3 for RGB, 4 for RGBA)
*
* @return Image Struct with width, height and raw data
*/
Image load_png_image(const char *pucFileName, unsigned char ucKeepAlpha, unsigned char ucPixelSize)
{
	Image image = {0, 0, NULL};
	
	/* Open file to read */
	FILE *PNG_file = fopen((char*)pucFileName, "rb");
	if (PNG_file == NULL)
	{
		return image;
	}
	
	/* Read header, check if it's a PNG */
	unsigned char PNG_header[PNG_HEADER_SIZE];
	fread(PNG_header, 1, PNG_HEADER_SIZE, PNG_file);
	if (png_sig_cmp(PNG_header, 0, PNG_HEADER_SIZE) != 0)
	{
		fclose(PNG_file);
		return image;
	}
	
	/* Create and initialize PNG read-struct */
	png_structp PNG_reader = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (PNG_reader == NULL)
	{
		fclose(PNG_file);
		return image;
	}
	
	/* Create and initialize PNG info-struct */
	png_infop PNG_info = png_create_info_struct(PNG_reader);
	if (PNG_info == NULL)
	{
		png_destroy_read_struct(&PNG_reader, NULL, NULL);
		fclose(PNG_file);
		return image;
	}
	
	/* Create and initialize another PNG info-struct */
	png_infop PNG_end_info = png_create_info_struct(PNG_reader);
	if (PNG_end_info == NULL)
	{
		png_destroy_read_struct(&PNG_reader, &PNG_info, NULL);
		fclose(PNG_file);
		return image;
	}
	
	/* setjmp() must be called in every function that calls a PNG-reading
	 * libpng function */
	if (setjmp(png_jmpbuf(PNG_reader)))
	{
		png_destroy_read_struct(&PNG_reader, &PNG_info, &PNG_end_info);
		fclose(PNG_file);
		return image;
	}
	
	/* Initialize IO-function in PNG and read info */
	png_init_io(PNG_reader, PNG_file);
	png_set_sig_bytes(PNG_reader, PNG_HEADER_SIZE);
	png_read_info(PNG_reader, PNG_info);
	
	/* Read image width and height and stor in the return-struct */
	png_uint_32 width, height;
	width = png_get_image_width(PNG_reader, PNG_info);
	height = png_get_image_height(PNG_reader, PNG_info);
	image.uiWidth = width;
	image.uiHeight = height;

	/* Read image bit-depth and color type */
	png_uint_32 bit_depth, color_type;
	bit_depth = png_get_bit_depth(PNG_reader, PNG_info);
	color_type = png_get_color_type(PNG_reader, PNG_info);
	
	/* Read the palette and convert it to RGB if one is used */
	if (color_type == PNG_COLOR_TYPE_PALETTE)
	{
			png_set_palette_to_rgb(PNG_reader);
	}
	
	/* Select gray-scale if used */
	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) 
	{
		png_set_gray_1_2_4_to_8(PNG_reader);
	}
	
	/* Convert gray to RGB if used */
	if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
	{
		png_set_gray_to_rgb(PNG_reader);
	}

	/* If alpha-channel is wanted, set appropriate settings */
	if (ucKeepAlpha)
	{
		if (png_get_valid(PNG_reader, PNG_info, PNG_INFO_tRNS))
		{
			png_set_tRNS_to_alpha(PNG_reader);
		}
		else
		{
			png_set_filler(PNG_reader, 0xff, PNG_FILLER_AFTER);
		}
	}
	else
	{
		if (color_type & PNG_COLOR_MASK_ALPHA)
		{
			png_set_strip_alpha(PNG_reader);
		}
	}
	
	/* 16-bit specific */
	if (bit_depth == 16)
	{
		png_set_strip_16(PNG_reader);
	}
	
	/* Store the new information to PNG_info */
	png_read_update_info(PNG_reader, PNG_info);
	
	/* Allocate memory */
	png_byte* PNG_image_buffer = (png_byte*)malloc(ucPixelSize * width * height);
	png_byte** PNG_rows = (png_byte**)malloc(height * sizeof(png_byte*));
	
	/* Set PNG_rows[n] to appropriate pointers in PNG_image_buffer */
	unsigned int row;
	for (row = 0; row < height; ++row)
	{
		PNG_rows[height - 1 - row] = PNG_image_buffer + (row * ucPixelSize * width);
	}
	
	/* Read image data */
	png_read_image(PNG_reader, PNG_rows);
	
	/* Free memory used by PNG_rows */
	free(PNG_rows);
	
	/* Free memory used by PNG-structs and close file */
	png_destroy_read_struct(&PNG_reader, &PNG_info, &PNG_end_info);
	fclose(PNG_file);
	
	/* Set pointer in return-struct to the buffer with the raw image */
	image.pucData = PNG_image_buffer;
	
	return image;
}


/**
* Loads a PNG texture in either RGB or RGBA-format
*
* @param ucFilename The path to the texture that is to be loaded
* @param uiDepth Depth of the texture GL_RGB or GL_RGBA
* @param ucFilter What type of filtering that is to be used: 0(MipMapped NN), 1(MipMapped Linear)
*
* @return tiTextures Structure with information about the texture
*/
textureInfo LoadTexturePNG(const char *pucFilename, unsigned int uiDepth, unsigned char ucFilter)
{
	unsigned char	*pucData;
	unsigned char	ucDepth = (uiDepth == GL_RGBA)? 4: 3,
						ucIsRGBA = (uiDepth == GL_RGBA)? 1: 0;

	Image image =  load_png_image(pucFilename, ucIsRGBA, ucDepth);
	textureInfo		tiTexture = {image.uiWidth, image.uiHeight, ucFilter, 0};
	
	/* Create texture with chosen filtering */
	glGenTextures(1, &tiTexture.gluiTexture);
	switch(ucFilter)
	{
		case 0: /* MipMapped Nearest Neighbor */
			glBindTexture(GL_TEXTURE_2D, tiTexture.gluiTexture);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
			gluBuild2DMipmaps(GL_TEXTURE_2D, uiDepth, image.uiWidth, image.uiHeight, uiDepth, GL_UNSIGNED_BYTE, image.pucData);
			break;

		case 1: /* MipMapped Linear */
			glBindTexture(GL_TEXTURE_2D, tiTexture.gluiTexture);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
			gluBuild2DMipmaps(GL_TEXTURE_2D, uiDepth, image.uiWidth, image.uiHeight, uiDepth, GL_UNSIGNED_BYTE, image.pucData);
			break;
	}

	/* free image data */
	free(image.pucData);

	return tiTexture;
	
}
